/*
    1.抽象出落地模块类
    2.派生出具体的落地子类, 标准输出落地子类, 定向文件落地子类, 滚动文件大小落地之类, 滚动文件时间落地子类;
    3.使用简单工厂模式让创建与表示分离
*/
#ifndef __L_SINK_H__
#define __L_SINK_H__

#include <iostream>
#include <string>
#include <fstream>
#include <sstream>
#include <memory>
#include <stdarg.h>
#include "util.hpp"

namespace Log
{
    //1.抽象出落地模块类
    class LogSink
    {
        public:
        using ptr = std::shared_ptr<LogSink>;
        LogSink()
        {}
        //日志输出接口
        virtual void Log(const char *data, size_t len) = 0;
        virtual ~LogSink(){} //析构函数作为虚函数是因为落地方式支持扩展, 用户扩展的落地方式中如果有需要释放的内容可以放到析构函数
    };

    //2.派生落地方式子类
    //标准输出落地子类
    class StdoutLogSink : public LogSink
    {
    public:
        //日志输出的接口
        void Log(const char* data, size_t len) override
        {
            // 不适用 std::cout<<输出是因为cout<<是通过判断字符串结尾 \0 来结束输出的;
            std::cout.write(data, len);
        }
    };

    //指定文件落地方式子类
    class FileLogSink : public LogSink
    {
    public:
        //传进来一个文件路径名, 构造时需要打开文件, 并将文件句柄(文件描述符)管理起来
        FileLogSink(const std::string& pathName)
        :_pathName(pathName)
        {
            //1.创建日志文件所在的目录
            util::file::CreateDir(util::file::Path(_pathName));
            // 2.创建并打开日志文件
            _ofs.open(_pathName, std::ios::binary | std::ios::app); //文件流默认是以写的方式打开, 我们要以追加的方式打开
            if(_ofs.is_open() == false)
            {
                std::cout << "文件打开失败!" << std::endl;
                abort();
            }
        }
        //日志输出的接口
        void Log(const char* data, size_t len) override
        {
            _ofs.write(data, len);
            if(_ofs.good() == false)
            {
                //判断是否写入成功
                std::cout << "文件写入失败!" << std::endl;
                abort();
            }
        }
        ~FileLogSink()
        {
            //使用结束后关闭文件流
            _ofs.close();
        }

    private:
        std::string _pathName;  //目标文件
        std::ofstream _ofs; //记录目标文件的句柄
    };

    //以文件大小来滚动的落地方式
    class RollByFileSizeLogSink : public LogSink
    {
    public:
        RollByFileSizeLogSink(const std::string& baseFName, size_t maxFSize)
        :_baseFName(baseFName), _maxFSize(maxFSize), _currFSize(0)
        {
            //生成文件扩展名
            std::string pathName =GenerateFileExtension();
            //创建文件所在的目录
            util::file::CreateDir(util::file::Path(pathName));
            // 打开文件
            _ofs.open(pathName, std::ios::binary | std::ios::app);
            if(_ofs.is_open() == false)
            {
                std::cout << "文件打开失败!" << std::endl;
                abort();
            }
        }
        //日志输出的接口
        void Log(const char* data, size_t len) override
        {
            if(_currFSize + len > _maxFSize)
            {
                //需要创建新的文件,先关闭旧的文件流
                _ofs.close();

                // 生成文件扩展名
                std::string pathName = GenerateFileExtension();
                //打开文件
                _ofs.open(pathName, std::ios::binary | std::ios::app);
                if(_ofs.is_open() == false)
                {
                    std::cout << "文件打开失败!" << std::endl;
                    abort();
                }
                _currFSize = 0; //创建新的文件后, 文件大小置为0
            }
            _ofs.write(data, len);
            if (_ofs.good() == false)
            {
                // 判断是否写入成功
                std::cout << "文件写入失败!" << std::endl;
                abort();
            }
            _currFSize += len;
        }
        ~RollByFileSizeLogSink()
        {
            _ofs.close();
        }

    private :
        // 生成文件扩展名
        std::string GenerateFileExtension()
        {
            //获取系统时间, 以时间来构造文件的扩展名
            time_t tm = util::Date::now();
            struct tm currtime;
            localtime_r(&tm, &currtime);
            std::stringstream fileExtensionName;
            fileExtensionName << _baseFName;
            fileExtensionName << currtime.tm_year + 1900;
            fileExtensionName << currtime.tm_mon + 1;
            fileExtensionName << currtime.tm_mday;
            fileExtensionName << currtime.tm_hour;
            fileExtensionName << currtime.tm_min;
            fileExtensionName << currtime.tm_sec;
            fileExtensionName << "-";
            fileExtensionName << _nameCount++;
            fileExtensionName << ".log";
            return fileExtensionName.str();
        }

    private:
        std::string _baseFName; // 基础文件名----最终的文件名采用基础文件名加扩展文件名, 扩展文件名是以时间来区分的;
        size_t _maxFSize;       // 滚动文件的大小阈值
        size_t _currFSize;  //当前文件的大小
        std::ofstream _ofs;  //记录目标文件的句柄
        // 由于写文件的速度相对来说还是比较快的并且1秒钟之内根据时间生成的文件扩展名是相同的, 当被写入的文件大小大于文件大小阈值的
        // 时候, 关闭后再次打开的文件是同一个文件, 所以就会出现多次打开同一个文件写入, 导致文件的大小大于设定的文件大小阈值了;
        // 所以在生成文件扩展名的时候增加一个计数器, 这样1秒钟之内生成的文件扩展名就不同了, 不会在超过文件大小阈值的时候打开同一个文件了;
        size_t _nameCount;
    };


    
    // 简单工厂模式 --- 让日志落地类的创建与表示分离
    // 简单工厂模式是违反开闭原则的, 用户扩展日志落地方式后会修改我们的源代码, 所以我们这里使用模板来实现不同落地类的创建;
    // 但是现在又产生了新的问题, 不同落地子类在构造的时候需要的参数数量是不同的, 所以我们这里使用可变参数包来满足不同落地子类的构造;
    class SinkFactory
    {
    public:
        using ptr = std::shared_ptr<SinkFactory>;
        template <class SinkType, class... Arags>
        static LogSink::ptr create(Arags &&...args)
        {
            return std::make_shared<SinkType>(std::forward<Arags>(args)...);
        }
    };
}
#endif